Skip to content

release: v1.14.0 — the worker takes its flags, and can be run as a service - #166

Merged
craftdevscommunity merged 3 commits into
mainfrom
release/v1.14.0
Sep 5, 2026
Merged

release: v1.14.0 — the worker takes its flags, and can be run as a service#166
craftdevscommunity merged 3 commits into
mainfrom
release/v1.14.0

Conversation

@hakeemRash

Copy link
Copy Markdown
Contributor

v1.14.0

Merging this pushes v1.14.0auto-release.yml fires on a push to main
touching CHANGELOG.md, reads the first versioned heading, tags, builds, and
lands the Homebrew formula bump itself.

Fixed — the worker ignored every flag it was given

hkm worker --queue=mails forwards its arguments verbatim to
app/worker/run.php, which read only WORKER_QUEUE from the environment. The
flag was accepted in silence and the worker drained default: no error, no
warning, a running process, and the wrong queue.

The entry point and the scaffolding template now parse -q/--queue,
-n/--max-iterations, --memory and -h/--help, each overriding the matching
environment variable, and exit 2 on an argument they do not recognise rather
than ignoring it. The environment fallbacks also moved from getenv() to
env(), since the loader injects .env into $_ENV and skips putenv() — so
getenv('WORKER_QUEUE') could not see a value set in the project's .env.

Added — hkm service

Generates the unit that supervises that worker, for whichever manager the host
runs.

hkm service [path|name]          preview — writes nothing
hkm service write                write it to <project>/var/service/
hkm service install [--start]    place it, reload the manager, optionally start
hkm service remove               stop, disable, delete

systemd or launchd (--platform overrides, so a Mac generates the Linux unit it
deploys); --system / --user choose the location, defaulting to system on
Linux and a user agent on macOS. --dry-run (-n) reports every write and
command for the three mutating verbs and performs none of them.

Three things the generated unit gets right that a hand-written one usually does
not:

  • ExecStart runs the launcher, hkm worker -p <root>, not php plus an
    absolute vendor/autoload.php — the launcher self-locates the kernel, so an
    upgrade that moves a version-stamped install directory cannot break the queue.
    --exec=php emits the direct form for a server with no launcher, and states
    the pinned autoload's cost in the unit.
  • TimeoutStopSec / ExitTimeOut is 90s — the worker traps SIGTERM and
    finishes the job in flight, and launchd's 20s default SIGKILLs it
    mid-transaction.
  • PATH and HKM_PHP_BIN are pinned — a service inherits none of a login
    shell's PATH and /opt/homebrew/bin is on neither manager's default. Without
    the pin the only diagnostic is error: FileNotFound, naming nothing. Found by
    running the generated unit, not by reading it.

Values reaching the unit are validated, not interpolated: a queue name may hold
only [A-Za-z0-9._:-]; ExecStart tokens with whitespace are quoted; plist
strings are XML-escaped.

Verified

  • Full launchd lifecycle on macOS: preview → write → install → --start
    launchctl print showing state = running with the process actually draining
    mails → remove, with nothing left behind. plutil -lint accepts the plist.
  • The --exec=php ExecStart line run under a stripped env -i with only the
    pinned variables.
  • Worker flags: long and short forms, env-only, flag-overrides-env, and the
    unknown-argument rejection.
  • zig build, zig build test (13 new unit tests), zig fmt --check.

Not verified: the systemd path could only be rendered and read here — no
Linux host. daemon-reload, enable --now and the sudo copy are unexercised.

`hkm worker --queue=mails` is a foreground process: it dies with the
terminal, it does not come back after a crash or a reboot, and nothing
collects its output. Every deployment therefore hand-writes the same unit
file, and hand-writing it is where the failures live.

`hkm service` generates that unit for whichever supervisor the host runs —
systemd or launchd, with --platform to override so a Mac can produce the
Linux unit it will deploy. Four verbs: preview (the default, writes
nothing), write, install [--start], remove. Scope is --system or --user,
defaulting to system on Linux and a user agent on macOS, where a
LaunchDaemon running as root is the wrong answer on a developer machine.

Three things the generated unit gets right that a hand-written one usually
does not:

- ExecStart runs the LAUNCHER, `hkm worker -p <root>`, not php plus an
  absolute vendor/autoload.php. The launcher self-locates the kernel, so a
  kernel upgrade that moves a version-stamped install directory cannot
  silently break the queue. `--exec=php` emits the direct form for a server
  with no launcher, and states the pinned autoload's cost in the unit.
- TimeoutStopSec / ExitTimeOut is 90s. The worker traps SIGTERM and
  finishes the job in flight before exiting — that is what makes a redeploy
  safe — and launchd's 20s default SIGKILLs it mid-transaction instead.
- PATH and HKM_PHP_BIN are pinned. A service inherits none of a login
  shell's PATH and /opt/homebrew/bin is on neither manager's default; the
  entire diagnostic without the pin is `error: FileNotFound`, with nothing
  anywhere naming php. This was found by running the generated unit, not by
  reading it.

Values that reach the unit are validated rather than interpolated: a queue
name may only contain [A-Za-z0-9._:-], so nothing can add an argument or a
directive; ExecStart tokens containing whitespace are quoted; plist strings
are XML-escaped.

--dry-run (-n) reports every write and every command for write/install/
remove and performs none of them. Its one filesystem touch is the
create-and-delete write probe in the destination directory, which is how it
knows whether to tell you sudo.
…rvice

`hkm worker --queue=mails` drained 'default' in silence: the entry point read
only WORKER_QUEUE from the environment and ignored every forwarded argument.
It now parses its flags, prefers them over the environment, and refuses an
argument it does not recognise.

`hkm service` generates the systemd unit or launchd plist that supervises that
worker — one command instead of the file every deployment was hand-writing,
with the stop timeout, the pinned PATH and the upgrade-proof ExecStart that a
hand-written one usually gets wrong.
@craftdevscommunity
craftdevscommunity merged commit d888179 into main Sep 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants